/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.geekyouup.android.animtimer;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;


/**
 * Animated view that draws the stopwatch, takes keystrokes, etc.
 */
class StopwatchView extends SurfaceView implements SurfaceHolder.Callback {
    class StopwatchThead extends Thread implements OnTouchListener {
        /*
         * State-tracking constants
         */
        public static final int STATE_PAUSE = 2;
        public static final int STATE_READY = 3;
        public static final int STATE_RUNNING = 4;
        
        private static final String KEY_STATE = "state";
        private static final String KEY_LASTTIME = "lasttime";
        private static final String KEY_NOWTIME = "currenttime";
        
        /*
         * Member (state) fields
         */
        /** The drawable to use as the background of the animation canvas */
        private Bitmap mBackgroundImage;      
        private Bitmap mBackgroundBottomImage; 
        private double mMinsAngle = 0;
        private double mSecsAngle = 0;
        private double mDisplayTimeMillis = 0;
        private double twoPI = Math.PI*2.0;
       
        private String mMillisPart ="000";
        private String mSecsPart="00";
        private String mMinsPart = "00";
        private String mHoursPart = "00"; 
        private String mFrames = "0";

        private int mCanvasWidth = 320;
        private int mCanvasHeight = 480;
        private int mSecsCenterX = 156;
        private int mSecsCenterY = 230;
        private int mSecsHandHalfLength = 0;
        private int mSecsHandHalfWidth=0;
        private int mTimeTextStartX=160;
        private int mTimeTextStartY=637;
        
        //private final int mMinsCenterX = 156;
        private int mMinsCenterY = 185;
        private int mMinsHandHalfLength = 0;
        private int mMinsHandHalfWidth = 0;
        
        private int mFPSState = 1; //0=18,1=24,2=30
        private static final int FPS_STATE_18=0;
        private static final int FPS_STATE_24=1;
        private static final int FPS_STATE_30=2;

        
        /** Used to figure out elapsed time between frames */
        private long mLastTime;

        /** Paint to draw the lines on screen. */
        private Paint mTextPaint;
        private Paint mFramesTextPaint;
        private Paint mSmallTextPaint;
        
        private Drawable mSecHand;
        private Drawable mMinHand;
        private Drawable mSecHandShadow;
        private Drawable mMinHandShadow;
        
        private Drawable m18Fps;
        private Drawable m24Fps;
        private Drawable m30Fps;
        
        /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
        private int mMode=STATE_READY;

        /** Indicate whether the surface has been created & is ready to draw */
        private boolean mRun = false;

        /** Handle to the surface manager object we interact with */
        private SurfaceHolder mSurfaceHolder;
        private AnimTimer mApp;
        private Context mContext;
        
        public StopwatchThead(SurfaceHolder surfaceHolder, Context context) {
            // get handles to some important objects
            mSurfaceHolder = surfaceHolder;
            mContext = context;
            
            Resources res = context.getResources();
            // load background image as a Bitmap instead of a Drawable b/c
            // we don't need to transform it and it's faster to draw this way
            mBackgroundImage = BitmapFactory.decodeResource(res,R.drawable.background);
            mBackgroundBottomImage = BitmapFactory.decodeResource(res,R.drawable.background_bottom);
            mSecHand = res.getDrawable(R.drawable.sechand);
            mMinHand = res.getDrawable(R.drawable.minhand);
            
            mSecHandShadow =res.getDrawable(R.drawable.sechand_shadow);
            mMinHandShadow = res.getDrawable(R.drawable.minhand_shadow);
            
            m18Fps = res.getDrawable(R.drawable.i18_fps);
            m24Fps = res.getDrawable(R.drawable.i24_fps);
            m30Fps = res.getDrawable(R.drawable.i30_fps);
            
            mMinsHandHalfLength = mMinHand.getIntrinsicHeight()/2;
            mMinsHandHalfWidth = mMinHand.getIntrinsicWidth()/2;
            mSecsHandHalfLength = mSecHand.getIntrinsicHeight()/2;
            mSecsHandHalfWidth = mSecHand.getIntrinsicWidth()/2;
            
            // Initialize paints for speedometer
            mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setTextSize(50);
            mTextPaint.setARGB(255,126,135,190);
            mTextPaint.setDither(true);
            
            mSmallTextPaint = new Paint();
            mSmallTextPaint.setAntiAlias(true);
            mSmallTextPaint.setTextSize(10);
            mSmallTextPaint.setARGB(255,128,128,128);
            mSmallTextPaint.setDither(true);
            
            mFramesTextPaint = new Paint();
            mFramesTextPaint.setAntiAlias(true);
            mFramesTextPaint.setTextSize(60);
            mFramesTextPaint.setARGB(255,255,255,255);
            mFramesTextPaint.setDither(true);
        }
        
        public void setApplication(AnimTimer mApp)
        {
        	this.mApp = mApp;
        }

        /**
         * Starts the game, setting parameters for the current difficulty.
         */
        public void doStart() {
            synchronized (mSurfaceHolder) {
                // First set the game for Medium difficulty
                mLastTime = System.currentTimeMillis();
                setState(STATE_RUNNING);
            }
        }

        /**
         * Pauses the physics update & animation.
         */
        public void pause() {
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
            }
        }

        public void reset()
        {
        	resetVars();
        }
        
        private void resetVars()
        {
        	synchronized (mSurfaceHolder) {
        		setState(STATE_PAUSE);
	        	mLastTime = 0;
	            mMinsAngle=0;
	            mSecsAngle=0;
	            mDisplayTimeMillis=0;
	            mMillisPart ="000";
	            mSecsPart="00";
	            mMinsPart = "00";
	            mHoursPart = "00";
        	}
        }
        
        public void setTime(int hour, int minute, int seconds)
        {
        	synchronized (mSurfaceHolder) {
	        	setState(STATE_READY);
	        	mLastTime = System.currentTimeMillis();
	            //mMinsAngle=(Math.PI*2*((double) minute/30.0));
	            //mSecsAngle=(Math.PI*2*((double) seconds/60.0));
	            //mDisplayTimeMillis=hour*3600000+minute*60000+seconds*1000;
	            //mMillisPart ="000";
	            //mSecsPart=(seconds<10?"0":"") + seconds;
	            //mMinsPart = (minute<10?"0":"") + minute;
	            //mHoursPart = (hour<10?"0":"")+hour;
	            
	            doStart();
        	}
        }
        
        @Override
        public void run() {
            while (mRun) {
                Canvas c = null;
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {
                        if (mMode == STATE_RUNNING) updatePhysics();
                        doDraw(c);
                    }
                } finally {
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }

        /**
         * Dump game state to the provided Bundle. Typically called when the
         * Activity is being suspended.
         * 
         * @return Bundle with this view's state
         */      
        public void saveState(SharedPreferences.Editor map) {
            synchronized (mSurfaceHolder) {
                if (map != null) {
                	if(mMode == STATE_RUNNING)
                	{
	                	map.putInt(KEY_STATE,mMode);
	                    map.putLong(KEY_LASTTIME,mLastTime);
	                    map.putLong (KEY_NOWTIME, (long) mDisplayTimeMillis);
                	}else
                	{
                		map.clear();
                	}
                }
            }
        }
        
        /**
         * Restores game state from the indicated Bundle. Typically called when
         * the Activity is being restored after having been previously
         * destroyed.
         * 
         * @param savedState Bundle containing the game state
         */       
        public synchronized void restoreState(SharedPreferences savedState) {
            synchronized (mSurfaceHolder) {
            	if(savedState!=null)
            	{
            		setState(savedState.getInt(KEY_STATE,STATE_PAUSE));
	                mLastTime = savedState.getLong(KEY_LASTTIME,System.currentTimeMillis());
	                mDisplayTimeMillis = savedState.getLong(KEY_NOWTIME,0);
	            }
            }
        }

        /**
         * Used to signal the thread whether it should be running or not.
         * Passing true allows the thread to run; passing false will shut it
         * down if it's already running. Calling start() after this was most
         * recently called with false will result in an immediate shutdown.
         * 
         * @param b true to run, false to shut down
         */
        public void setRunning(boolean b) {
            mRun = b;
        }

        public boolean isRunning()
        {
        	return mRun;
        }
        
        /**
         * Sets the game mode. That is, whether we are running, paused, in the
         * failure state, in the victory state, etc.
         * 
         * @param mode one of the STATE_* constants
         */
        public void setState(int mode) {
            synchronized (mSurfaceHolder) {
                mMode = mode;    
            }
        }
       
        /**
         * Resumes from a pause.
         */
        public void unpause() {
            // Move the real time clock up to now
            synchronized (mSurfaceHolder) {
                mLastTime = System.currentTimeMillis();
            }
            setState(STATE_RUNNING);
        }

        /**
         * Draws the ship, fuel/speed bars, and background to the provided
         * Canvas.
         */
        private void doDraw(Canvas canvas) {
            // Draw the background image. Operations on the Canvas accumulate
            // so this is like clearing the screen.
        	canvas.drawColor(Color.WHITE);
            canvas.drawBitmap(mBackgroundImage, 0,0, null);
            canvas.drawBitmap(mBackgroundBottomImage, 0,mCanvasHeight-mBackgroundBottomImage.getHeight(), null);
            
            //draw lap times
            if(mMode!=STATE_READY)
            {
            	String timeText = mHoursPart+":"+mMinsPart+":"+mSecsPart+":"+mMillisPart;
            	//mTextPaint.getTextBounds(timeText, 0, timeText.length(), textRect);
            	canvas.drawText(timeText,mCanvasWidth-312, mCanvasHeight-125, mTextPaint);
            	
            	Rect textRect = new Rect();
            	mFramesTextPaint.getTextBounds(mFrames, 0, mFrames.length(), textRect);
            	canvas.drawText(mFrames,mCanvasWidth-30-textRect.width(),mCanvasHeight-40,mFramesTextPaint);
            }
            
            if(mFPSState==FPS_STATE_18)
            {
            	m18Fps.setBounds(0, mCanvasHeight-m18Fps.getIntrinsicHeight(), m18Fps.getIntrinsicWidth(), mCanvasHeight);
            	m18Fps.draw(canvas);
            }else if(mFPSState==FPS_STATE_24)
            {
            	m24Fps.setBounds(0, mCanvasHeight-m24Fps.getIntrinsicHeight(), m24Fps.getIntrinsicWidth(), mCanvasHeight);
            	m24Fps.draw(canvas);
            }else
            {
            	m30Fps.setBounds(0, mCanvasHeight-m30Fps.getIntrinsicHeight(), m30Fps.getIntrinsicWidth(), mCanvasHeight);
            	m30Fps.draw(canvas);
            }
             
            // Draw the secs hand with its current rotation
            canvas.save();
            canvas.rotate((float) Math.toDegrees(mSecsAngle), mSecsCenterX,mSecsCenterY);
            
            mSecHand.setBounds(mSecsCenterX-mSecsHandHalfWidth, mSecsCenterY-mSecsHandHalfLength, mSecsCenterX+mSecsHandHalfWidth, mSecsCenterY+mSecsHandHalfLength);
            mSecHand.draw(canvas);
            
            mSecHandShadow.setBounds(mSecsCenterX-mSecsHandHalfWidth-1, mSecsCenterY-mSecsHandHalfLength+3, mSecsCenterX+mSecsHandHalfWidth-1, mSecsCenterY+mSecsHandHalfLength+3);
            mSecHandShadow.draw(canvas);
            canvas.restore();
            
            //draw the mins hand with its current rotatiom
            canvas.save();
            canvas.rotate((float) Math.toDegrees(mMinsAngle), mSecsCenterX,mMinsCenterY);
            mMinHand.setBounds(mSecsCenterX-mMinsHandHalfWidth, mMinsCenterY-mMinsHandHalfLength, mSecsCenterX+mMinsHandHalfWidth, mMinsCenterY+mMinsHandHalfLength);
            mMinHand.draw(canvas);
            
            mMinHand.setBounds(mSecsCenterX-mMinsHandHalfWidth-1, mMinsCenterY-mMinsHandHalfLength+2, mSecsCenterX+mMinsHandHalfWidth-1, mMinsCenterY+mMinsHandHalfLength+2);
            mMinHandShadow.draw(canvas);
            
            canvas.restore();
        }

        
        /**
         * Figures the bike state (x, y, ...) based on the passage of
         * realtime. Does not invalidate(). Called at the start of draw().
         * Detects the end-of-game and sets the UI to the next state.
         */
        private void updatePhysics() {
        	try {	sleep(20);	} catch (InterruptedException e) {}
            long now = System.currentTimeMillis();
                
            mDisplayTimeMillis += (now - mLastTime);
            
            mMinsAngle=twoPI*(mDisplayTimeMillis/50000.0); //mins is 0 to 30
            mSecsAngle=twoPI*(mDisplayTimeMillis/10000.0);
            
            int numHours = (int) Math.floor(mDisplayTimeMillis/9801000);
            mHoursPart = (numHours<10?"0":"")+numHours;
            
            int numMins = (int) Math.floor(mDisplayTimeMillis/99000 - numHours*99);
            mMinsPart = (numMins<10?"0":"") + numMins;
            
            int numSecs = (int) Math.floor(mDisplayTimeMillis/1000 - numMins*99000-numHours*9801000);
            mSecsPart = (numSecs<10?"0":"") + numSecs;
            
            int numMillis = ((int)(mDisplayTimeMillis-numHours*9801000-numMins*99000-numSecs*1000));
            mMillisPart = (numMillis<10?"00":(numMillis<100?"0":"")) + numMillis;
            
            int numFrames = mFPSState==FPS_STATE_18?18:(mFPSState==FPS_STATE_24?24:30);
            mFrames = ((int)(mDisplayTimeMillis/1000*numFrames))+"";
            
            mLastTime = now;
        }

		public boolean onTouch(View v, MotionEvent event) {
			if(event.getAction()==MotionEvent.ACTION_DOWN)
			{
				if(event.getY()<mCanvasHeight-mBackgroundBottomImage.getHeight()) //pause at top of screen
				{
					goPauseUnpause();
				}else// switch FPS state
				{
					mFPSState = (mFPSState+1)%3;
					
		            int numFrames = mFPSState==FPS_STATE_18?18:(mFPSState==FPS_STATE_24?24:30);
		            mFrames = ((int)(mDisplayTimeMillis/1000*numFrames))+"";
				}
			}
			return true;
		}
		
		public void goPauseUnpause()
		{
			if(mApp != null) mApp.removeSplashText();
			
			if(mMode == STATE_PAUSE)
			{
				unpause();
			}else if(mMode == STATE_RUNNING)
			{
				pause();
			}else{
				doStart();
			}
		}
		
		public String getTime()
		{
			return mHoursPart+":"+mMinsPart+":"+mSecsPart+"."+mMillisPart;
		}
		
        /* Callback invoked when the surface dimensions change. */
        public void setSurfaceSize(int width, int height) {
            // synchronized to make sure these all change atomically
            synchronized (mSurfaceHolder) {
                //mCanvasWidth = width;
                mCanvasHeight = height;
                mCanvasWidth=width;

                mSecsCenterX = width/2+1;
                //Log.d("animtimer","width: " + width + ", height: " + height);
                if(height>600)
                {
                	mSecsCenterY = 387;
                	mMinsCenterY = 317;
                	//Log.d("animtimer","setting hands to " + mSecsCenterX + " x " + mSecsCenterY);
                }
            }
        }
    }

    /** Handle to the application context, used to e.g. fetch Drawables. */
    private StopwatchThead thread;
    public StopwatchView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // register our interest in hearing about changes to our surface
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

        // create thread only; it's started in surfaceCreated()
        thread = new StopwatchThead(holder, context);
        setOnTouchListener(thread);
        setFocusableInTouchMode(true);
        setFocusable(true); // make sure we get key events
    }

    /**
     * Fetches the animation thread corresponding to this LunarView.
     * 
     * @return the animation thread
     */
    public StopwatchThead getThread() {
        return thread;
    }
    
    public StopwatchThead createNewThread(Context context)
    {
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
    	
        thread = new StopwatchThead(holder, context);
        setOnTouchListener(thread);
        return thread;
    }

    /* Callback invoked when the surface dimensions change. */
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        thread.setSurfaceSize(width, height);
    }

    /*
     * Callback invoked when the Surface has been created and is ready to be
     * used.
     */
    public void surfaceCreated(SurfaceHolder holder) {
        // start the thread here so that we don't busy-wait in run()
        // waiting for the surface to be created
    	try
    	{
	    	if(!thread.isRunning())
	    	{
		    	thread.setRunning(true);
		        thread.start();
	    	}
    	}catch(Exception e)
    	{		
    	}
    }

    /*
     * Callback invoked when the Surface has been destroyed and must no longer
     * be touched. WARNING: after this method returns, the Surface/Canvas must
     * never be touched again!
     */
    public void surfaceDestroyed(SurfaceHolder holder) {
        // we have to tell thread to shut down & wait for it to finish, or else
        // it might touch the Surface after we return and explode
        boolean retry = true;
        thread.setRunning(false);
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }
    }
}
